using System.Collections.Generic;
using System.IO;
using System.Text;

namespace NFreeMarker.Template.Utility
{
	/**
	 * Performs an HTML escape of a given template fragment. Specifically,
	 * &lt; &gt; &quot; and &amp; are all turned into entities.
	 *
	 * <p>Usage:<br />
	 * From java:</p>
	 * <pre>
	 * SimpleHash root = new SimpleHash();
	 *
	 * root.put( "htmlEscape", new freemarker.template.utility.HtmlEscape() );
	 *
	 * ...
	 * </pre>
	 *
	 * <p>From your FreeMarker template:</p>
	 * <pre>
	 *
	 * The following is HTML-escaped:
	 * &lt;transform htmlEscape&gt;
	 *   &lt;p&gt;This paragraph has all HTML special characters escaped.&lt;/p&gt;
	 * &lt;/transform&gt;
	 *
	 * ...
	 * </pre>
	 *
	 * @version $Id: HtmlEscape.java,v 1.29 2003/02/25 00:28:16 revusky Exp $
	 * @see freemarker.template.utility.XmlEscape
	 */
	public class HtmlEscape : ITemplateTransformModel
    {
	    private static readonly char[] Lt = "&lt;".ToCharArray();
	    private static readonly char[] Gt = "&gt;".ToCharArray();
	    private static readonly char[] Amp = "&amp;".ToCharArray();
	    private static readonly char[] Quot = "&quot;".ToCharArray();

	    public TextWriter GetWriter(TextWriter output, IDictionary<string, ITemplateModel> args)
	    {
	        return new HtmlEscapeWriter(output);
	    }

	    private class HtmlEscapeWriter : TextWriter
	    {
	        private readonly TextWriter _output;

	        public HtmlEscapeWriter(TextWriter output)
	        {
	            _output = output;
	        }

	        public override Encoding Encoding
	        {
	            get { return _output.Encoding; }
	        }

        	public override void Write(int c)
            {
                switch ((char) c)
                {
                    case '<':
                        _output.Write(Lt, 0, 4);
                        break;
                    case '>':
                        _output.Write(Gt, 0, 4);
                        break;
                    case '&':
                        _output.Write(Amp, 0, 5);
                        break;
                    case '"':
                        _output.Write(Quot, 0, 6);
                        break;
                    default:
                        _output.Write(c);
                        break;
                }
            }

            public override void Write(char[] cbuf, int off, int len)
            {
                int lastoff = off;
                int lastpos = off + len;
                for (int i = off; i < lastpos; i++)
                {
                    switch (cbuf[i])
                    {
                        case '<':
                            _output.Write(cbuf, lastoff, i - lastoff);
                            _output.Write(Lt, 0, 4); lastoff = i + 1;
                            break;
                        case '>':
                            _output.Write(cbuf, lastoff, i - lastoff);
                            _output.Write(Gt, 0, 4); lastoff = i + 1;
                            break;
                        case '&':
                            _output.Write(cbuf, lastoff, i - lastoff);
                            _output.Write(Amp, 0, 5); lastoff = i + 1;
                            break;
                        case '"':
                            _output.Write(cbuf, lastoff, i - lastoff);
                            _output.Write(Quot, 0, 6); lastoff = i + 1;
                            break;
                    }
                }
                int remaining = lastpos - lastoff;
                if (remaining > 0)
                {
                    _output.Write(cbuf, lastoff, remaining);
                }
            }

            public override void Flush()
            {
                _output.Flush();
            }

            public override void Close() {
            }
	    }
    }
}